﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework;

namespace DARE
{
    /// <summary>
    /// The game objects are managed by the CGameObjectMgr.
    /// They have to be registered as game object templates on the CGameObjectMgr ( CGameObjectMgr.RegisterGameObject() ).
    /// The game object registered isn't the one you'll be using. You have to instantiate the game object by calling CGameObjectMgr.Instantiate() method.
    /// </summary>
    [AttrTypeDependentSerializable(typeof(CGameObjectData))]
    public class CGameObject
    {
        #region identifiers

        public delegate void DGameObjectEventHandler(CGameObject gameObject);
        public delegate void DGameObjectKeyEventHandler(CGameObject gameObject, CKeyboardEvent key);
        public delegate void DGameObjectMouseButtonEventHandler(CGameObject gameObject, CMouse.EButton button);
        public delegate void DGameObjectCollisionEventHandler(CGameObject gameObject, CCollider collider);

        #endregion

        #region fields

        [ContentSerializerIgnore]
        protected CGameObjectMgr m_mgr;
        [ContentSerializer]
        private uint m_id = 0;
        protected string m_name;
        [ContentSerializerIgnore]
        protected CNode m_node;
        protected bool m_isLeftClicked = false;
        protected bool m_isRightClicked = false;
        protected bool m_isMiddleClicked = false;
        protected bool m_isMouseOver = false;
        [ContentSerializerIgnore]
        protected Texture2D m_icon;
        protected string m_tag;
        [ContentSerializer]
        protected string m_iconAssetName;
        [ContentSerializerIgnore]
        protected Dictionary<Type, CComponent> m_components;
        protected bool m_isInitialized = false;
        /// <summary>
        /// If m_isSelfish is set to true, the events will be invoked
        /// only if the mouse is over this GameObject.
        /// Else, the events will be invoked on each input events.
        /// </summary>
        [ContentSerializer]
        protected bool m_isSelfish = true;
        [ContentSerializer]
        protected string m_gameObjectTemplateName;

        public event DGameObjectEventHandler OnUpdate;
        public event DGameObjectEventHandler OnInitialize;
        public event DGameObjectCollisionEventHandler OnCollisionEnter;
        public event DGameObjectCollisionEventHandler OnCollisionExit;
        public event DGameObjectMouseButtonEventHandler OnClick;
        public event DGameObjectMouseButtonEventHandler OnRelease;
        public event DGameObjectMouseButtonEventHandler OnMouseDown;
        public event DGameObjectMouseButtonEventHandler OnMouseUp;
        public event DGameObjectEventHandler OnMouseOverEnter;
        public event DGameObjectEventHandler OnMouseOverExit;
        public event DGameObjectKeyEventHandler OnKeyPress;
        public event DGameObjectKeyEventHandler OnKeyRelease;
        public event DGameObjectEventHandler OnAction;


        public BoundingBox m_SizeOfZoneWhereActionable;
        public BoundingBox m_zoneWhereActionable;

        #endregion

        #region properties

        public bool IsActive { get; set; }
        [ContentSerializerIgnore]
        public DARE.CVignette Vignette { get; set; }

        public BoundingBox ZoneWhereActionable { get { return m_zoneWhereActionable; } set { m_SizeOfZoneWhereActionable = value; m_zoneWhereActionable = value; } }
        public bool MoveActionZoneWithNode { get; set; }

        public string IconAssetName { get { return m_iconAssetName; } }
        [ContentSerializerIgnore]
        public Texture2D Icon { get { return m_icon; } set { m_icon = value; m_iconAssetName = CResourceMgr.GetResourceName(m_icon); } }
        public string GameObjectTemplateName { get { return m_gameObjectTemplateName; } }
        [ContentSerializerIgnore]
        public bool IsInitialized { get { return m_isInitialized; } set { m_isInitialized = false; } }
        /// <summary>
        /// If IsSelfish is set to true, the events will be invoked
        /// only if the mouse is over this GameObject.
        /// Else, the events will be invoked on each input events.
        /// </summary>
        public bool IsSelfish { get { return m_isSelfish; } set { m_isSelfish = value; } }
        [ContentSerializerIgnore]
        public CGameObjectMgr Mgr { get { return m_mgr; } set { if (m_mgr != value) { m_mgr = value; if (m_mgr != null)m_id = m_mgr.LastDisponibleId; } } }
        public uint ID { get { return m_id; } set { m_id = value; } }
        public string Name { get { return m_name; } set { m_name = value; } }
        [ContentSerializerIgnore]
        public CNode Node { get { return m_node; } set { m_node = value; if (m_node != null && m_gameObjectTemplateName == null) m_node.IsActive = false; } }
        public bool IsLeftClicked { get { return m_isLeftClicked; } set { m_isLeftClicked = value; } }
        public bool IsRightClicked { get { return m_isRightClicked; } set { m_isRightClicked = value; } }
        public bool IsMiddleClicked { get { return m_isMiddleClicked; } set { m_isMiddleClicked = value; } }
        public bool IsMouseOver { get { return m_isMouseOver; } set { m_isMouseOver = value; } }
        public string Tag { get { return m_tag; } set { m_tag = value; } }

        #endregion

        #region ctor

        public CGameObject(string name)
        {
            m_name = name;
            m_components = new Dictionary<Type, CComponent>();
            m_zoneWhereActionable = new BoundingBox(-Vector3.One, Vector3.One);
            m_SizeOfZoneWhereActionable = new BoundingBox(-Vector3.One, Vector3.One);
            MoveActionZoneWithNode = true;
        }

        #endregion

        #region methods

        public virtual void Action()
        {
            if (OnAction != null)
                OnAction.Invoke(this);
        }

        public List<CComponent> GetComponentList()
        {
            return m_components.Values.ToList();
        }

        public CGameObject AddComponent(CComponent component)
        {
            if (!m_components.ContainsKey(component.GetType()))
                m_components.Add(component.GetType(), component);
            else
                m_components[component.GetType()] = component;
            OnInitialize += component.Initialize;
            OnUpdate += component.Update;
            return this;
        }

        public CGameObject RemoveComponent(CComponent component)
        {
            if (m_components.ContainsValue(component))
                m_components.Remove(component.GetType());
            OnInitialize -= component.Initialize;
            OnUpdate -= component.Update;
            return this;
        }

        public CGameObject RemoveComponent(Type type)
        {
            if (m_components.ContainsKey(type))
            {
                OnInitialize -= m_components[type].Initialize;
                OnUpdate -= m_components[type].Update;
                m_components.Remove(type);
            }
            return this;
        }

        public T GetComponent<T>()
             where T : class
        {
            if (m_components.ContainsKey(typeof(T)))
                return m_components[typeof(T)] as T;
            return null;
        }

        public virtual void Highlight()
        {
            if (Node.Entity.Model != null)
                Node.Entity.Model.Highlight();
        }

        /// <summary>
        /// Clone this GameObject and all its components.
        /// (Uses CNode.ShalowClone).
        /// </summary>
        /// <returns>the cloned GameObject</returns>
        /// <seealso cref="CGameObjectMgr.Instantiate"/>
        public virtual object Clone()
        {
            object obj = this.MemberwiseClone();
            // Reinitialization of each values that shouldn't be cloned
            ((CGameObject)obj).OnClick = null;
            ((CGameObject)obj).OnCollisionEnter = null;
            ((CGameObject)obj).OnCollisionExit = null;
            ((CGameObject)obj).OnInitialize = null;
            ((CGameObject)obj).OnKeyPress = null;
            ((CGameObject)obj).OnKeyRelease = null;
            ((CGameObject)obj).OnMouseDown = null;
            ((CGameObject)obj).OnMouseOverEnter = null;
            ((CGameObject)obj).OnMouseOverExit = null;
            ((CGameObject)obj).OnMouseUp = null;
            ((CGameObject)obj).OnRelease = null;
            ((CGameObject)obj).OnUpdate = null;
            ((CGameObject)obj).ZoneWhereActionable = ZoneWhereActionable;
            ((CGameObject)obj).MoveActionZoneWithNode = MoveActionZoneWithNode;
            if (Vignette != null)
                ((CGameObject)obj).Vignette = Vignette.Clone() as CVignette;

            m_isLeftClicked = false;
            m_isRightClicked = false;
            m_isMiddleClicked = false;
            m_isMouseOver = false;

            ((CGameObject)obj).m_gameObjectTemplateName = m_name;
            ((CGameObject)obj).m_isInitialized = false;
            ((CGameObject)obj).m_id = m_mgr.LastDisponibleId;
            if (m_node != null)
                ((CGameObject)obj).m_node = m_node.ShalowClone();
            ((CGameObject)obj).m_components = new Dictionary<Type, CComponent>();
            foreach (CComponent c in m_components.Values)
                c.Clone((CGameObject)obj);
            return obj;
        }

        public virtual void Destroy()
        {
            CDare.Instance.InputMgr.Mouse.RemoveListener(this);
            CDare.Instance.InputMgr.Keyboard.RemoveListener(this);
            m_mgr.UnregisterInstantiation(this);
            m_node.Render3D.RemoveNode(m_node);
        }

        public virtual void Update()
        {
            if (OnUpdate != null)
                OnUpdate.Invoke(this);
            if (MoveActionZoneWithNode)
            {
                m_zoneWhereActionable = new BoundingBox(m_node.Position + m_SizeOfZoneWhereActionable.Min, m_node.Position + m_SizeOfZoneWhereActionable.Max);
            }
            bool isOver = false;
            if (m_node != null && m_node.Render3D != null && CDare.Instance.InputMgr.Mouse.MouseIntersectInfo.ContainsKey(m_node.Render3D) && CDare.Instance.InputMgr.Mouse.MouseIntersectInfo[m_node.Render3D].ContainsKey(m_node))
            {
                isOver = true;
                if (!m_isMouseOver)
                    OnMouseOverMethod();
            }
            if (m_isMouseOver && !isOver)
                OnMouseLeftOverMethod();
            if (m_isLeftClicked)
                OnMouseDownMethod(CMouse.EButton.LEFT);
            else if (!m_isLeftClicked)
                OnMouseUpMethod(CMouse.EButton.LEFT);
            if (m_isRightClicked)
                OnMouseDownMethod(CMouse.EButton.RIGHT);
            else if (!m_isRightClicked)
                OnMouseUpMethod(CMouse.EButton.RIGHT);
            if (m_isMiddleClicked)
                OnMouseDownMethod(CMouse.EButton.MIDDLE);
            else if (!m_isMiddleClicked)
                OnMouseUpMethod(CMouse.EButton.MIDDLE);
        }

        public virtual void Initialize()
        {
            CDare.Instance.InputMgr.Mouse.RegisterGameObjectOnButton(this, CMouse.EButton.LEFT);
            CDare.Instance.InputMgr.Mouse.RegisterGameObjectOnButton(this, CMouse.EButton.RIGHT);
            CDare.Instance.InputMgr.Mouse.RegisterGameObjectOnButton(this, CMouse.EButton.MIDDLE);
#if WINDOWS
            foreach (Keys k in System.Enum.GetValues(typeof(Keys)))
                CDare.Instance.InputMgr.Keyboard.RegisterGameObjectOnKey(this, new CKeyboardEvent(k));
#endif

            if (OnInitialize != null)
                OnInitialize.Invoke(this);
            m_isInitialized = true;
        }

        public virtual bool OnKeyPressedMethod(CKeyboardEvent key)
        {
            if (OnKeyPress != null) 
                OnKeyPress.Invoke(this, key);
            return true;
        }

        public virtual bool OnKeyReleasedMethod(CKeyboardEvent key)
        {
            if (OnKeyRelease != null)
                OnKeyRelease.Invoke(this, key);
            return true;
        }

        public virtual bool OnClickMethod(CMouse.EButton button)
        {
            if (OnClick != null)
                OnClick.Invoke(this, button);
            if (button == CMouse.EButton.LEFT)
                m_isLeftClicked = true;
            else if (button == CMouse.EButton.RIGHT)
                m_isRightClicked = true;
            else if (button == CMouse.EButton.MIDDLE)
                m_isMiddleClicked = true;

            return true;
        }

        public virtual bool OnReleaseMethod(CMouse.EButton button)
        {
            if (OnRelease != null)
                OnRelease.Invoke(this, button);
            if (button == CMouse.EButton.LEFT)
                m_isLeftClicked = false;
            else if (button == CMouse.EButton.RIGHT)
                m_isRightClicked = false;
            else if (button == CMouse.EButton.MIDDLE)
                m_isMiddleClicked = false;
            return true;
        }

        public virtual bool OnMouseDownMethod(CMouse.EButton button)
        {
            if (OnMouseDown != null)
                OnMouseDown.Invoke(this, button);
            return true;
        }

        public virtual bool OnMouseUpMethod(CMouse.EButton button)
        {
            if (OnMouseUp != null)
                OnMouseUp.Invoke(this, button);
            return true;
        }

        public virtual bool OnMouseOverMethod()
        {
            if (OnMouseOverEnter != null)
                OnMouseOverEnter.Invoke(this);
            m_isMouseOver = true;
            return true;
        }

        public virtual bool OnMouseLeftOverMethod()
        {
            if (OnMouseOverExit != null)
                OnMouseOverExit.Invoke(this);
            m_isMouseOver = false;
            return true;
        }

        public virtual void OnCollisionEnterMethod(CCollider collider)
        {
            if (OnCollisionEnter != null)
                OnCollisionEnter.Invoke(this, collider);
        }

        public virtual void OnCollisionExitMethod(CCollider collider)
        {
            if (OnCollisionExit != null)
                OnCollisionExit.Invoke(this, collider);
        }

        #endregion
    }
}
